package client

import (
	"context"
	"encoding/binary"
	"fmt"
	"go_server/common"
	"io"
	"net"
	"os"
	"os/signal"
	"sync"
	"sync/atomic"
	"syscall"

	"github.com/astaxie/beego/logs"
)

// 写完相关测试用例-并推送 √
// 完成客户端API的一些实现---不考虑引入buffer
// 学习一下go中的io操作
// 继续gorm的使用

//搭建客户端相关API

const (
	ReadPkgChanBufferNum  = 1024
	WritePkgChanBufferNum = 1024
)

type ClientSession struct {
	Conn net.Conn

	Group sync.WaitGroup

	//读写chan
	ReadPkg  chan *common.PackageStruct
	WritePkg chan *common.PackageStruct

	//停止chan
	StopChan chan any
	IsStop   atomic.Bool
}

//还是遵循包头加包体读取的方式吧

func CreateClientSession(proto string, ipPort string) *ClientSession {
	clietSession := new(ClientSession)
	con, err := net.Dial(proto, ipPort)
	if err != nil {
		panic("远端连接失败：" + err.Error())

	}
	//初始化clientsession的参数
	clietSession.Conn = con
	clietSession.Group = sync.WaitGroup{}
	clietSession.ReadPkg = make(chan *common.PackageStruct, ReadPkgChanBufferNum)
	clietSession.WritePkg = make(chan *common.PackageStruct, WritePkgChanBufferNum)
	clietSession.IsStop.Store(false)
	clietSession.StopChan = make(chan any, 1) //设置一个缓冲区

	//开启两类协程，一个读，一个写，指定数量的handlerNum（目前注释掉了）处理
	clietSession.Group.Add(2)
	go readGoroutine(clietSession)

	go writeGoroutine(clietSession)

	// for i := 0; i < handlerNum; i++ {
	// 	clietSession.Group.Add(1)
	// 	go handlerMsgGoroutine(clietSession)
	// }

	return clietSession
}

// 不用在等待了，提供显示关闭的api
func (c *ClientSession) WaitingAndCloseCon(context context.Context) {
	stopsignal := make(chan os.Signal, 1)
	var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGINT}
	signal.Notify(stopsignal, shutdownSignals...)
	//可能会由拓展
	select {
	case sign := <-stopsignal:
		fmt.Println("捕获到信号：", sign)
		logs.Info("捕获到信号：", sign)
	case <-c.StopChan:
		fmt.Println("内部发送错误导致关闭连接")
	case <-context.Done(): //这个是为了做测试用的，实际上可以没有
		break
	}
	if c.IsStop.CompareAndSwap(false, true) {
		close(c.StopChan)
	}

	c.Conn.Close()
	c.Group.Wait()
	fmt.Println("相关协程已经关闭！！")
}

func (c *ClientSession) CloseRpc() {
	c.Conn.Close() //这个close后，自然对应的StopChan也会关闭，然后所有协程也会结束
	c.Group.Wait()
	fmt.Println("相关协程已经关闭！！")
}

func prasePkgHead(fullMsg *common.PackageStruct, headbuff []byte) error {
	fullMsg.Head = new(common.PackageHead)
	fullMsg.Head.PkgId = binary.BigEndian.Uint16(headbuff[0:2]) //从缓冲区 impl.buff 的当前位置 impl.pos 开始，读取 2 个字节，并将其按照大端序（Big-Endian）的规则解析为一个 uint16 类型的整数值
	fullMsg.Head.PkgBodyLen = binary.BigEndian.Uint32(headbuff[2:6])
	return nil
}

func readHandler(con net.Conn) (*common.PackageStruct, error) {
	//读包头，再读包体
	headBuff := make([]byte, 6) //这里直接硬编码
	_, err := io.ReadAtLeast(con, headBuff[:6], 6)
	if err != nil {
		return nil, common.ErrRead
	}
	//那么进行解析，并拼装
	fullMsg := new(common.PackageStruct)

	err = prasePkgHead(fullMsg, headBuff)
	if err != nil {
		return nil, common.ErrParseHead
	}
	//然后读读取包体
	fullMsg.Body = make([]byte, fullMsg.Head.PkgBodyLen)

	_, err = io.ReadFull(con, fullMsg.Body)
	if err != nil {
		return nil, common.ErrReadBody
	}
	//fmt.Println("成功读取：", fullMsg.Head.PkgId)

	return fullMsg, nil
}
func readGoroutine(c *ClientSession) {

	defer c.Group.Done()
	for {
		select {
		case <-c.StopChan:
			return
		default:
			pkg, err := readHandler(c.Conn) //
			if err != nil {
				//如果读取失败了，那么也close掉c.StopChan,让其余都结束吧
				if c.IsStop.CompareAndSwap(false, true) {
					close(c.StopChan)
				}
				return
			}
			//这里不再放入到同一个channel中了，根据id有对应的channel
			//c.ReadPkg <- pkg
			channel, ok := ClientMsgHandleMapInstance.ChannelMap[pkg.Head.PkgId]
			if !ok {
				logs.Info("[client.go:158]未找到对应的channel,返回值被丢失,可能是超时了")
			} else {
				channel <- pkg.Body //将body写入即可
			}

		}
	}

}

func writePackageForClient(con net.Conn, pkg *common.PackageStruct) error {
	//

	headBuffer := make([]byte, 6)
	binary.BigEndian.PutUint16(headBuffer[0:2], pkg.Head.PkgId)
	binary.BigEndian.PutUint32(headBuffer[2:6], pkg.Head.PkgBodyLen)

	buffers := net.Buffers{headBuffer, pkg.Body} //避免创建新的切片

	//能保证切片已经发送到缓冲区了
	if _, err := buffers.WriteTo(con); err != nil {
		// 处理错误
		return err
	}
	return nil

}

func writeGoroutine(c *ClientSession) {
	defer c.Group.Done()
	for {
		select {
		case <-c.StopChan:
			return
		case pkg := <-c.WritePkg:
			err := writePackageForClient(c.Conn, pkg)
			if err != nil {
				if c.IsStop.CompareAndSwap(false, true) {
					close(c.StopChan)
				}
				fmt.Println("[client.go:78]WritePackageForClient 返回error：", err.Error())
				return
			}
		}
	}

}

// // 这个已经没用了
// func handlerMsgGoroutine(c *ClientSession) {
// 	defer c.Group.Done()

// 	for {
// 		select {
// 		case <-c.StopChan:
// 			return
// 		case pkg := <-c.ReadPkg:
// 			clientFindFuncAndCall(c, pkg) //这个函数的存在是为了实现异步调用--目前没有实现
// 		}
// 	}

// }
